#include "../../controls/OrbitControl.hpp"
#include "Pocelain.h"

#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <memory>
#include <string>

using namespace std;
POCELAIN_NAMESPACE_USING
string shader_vert = R"EOF(
layout(location=0) in vec3 position;
layout(location=1) in vec3 normal;
layout(location=2) in vec2 uv;
uniform mat4x4 modelViewMatrix;
uniform mat4x4 projectionMatrix;
uniform mat3x3 normalMatrix;
out vec3 vNormal;
out vec2 vUv;
out vec3 vWorldPosition;
void main()
{
    vUv = uv;
    vNormal = normalize(normalMatrix * normal);
    vWorldPosition = (modelViewMatrix * vec4(position,1.0)).xyz;
    gl_Position = projectionMatrix*vec4 (vWorldPosition,1.0);

})EOF";

string shader_frag = R"EOF(
out vec4 color;
in vec3 vNormal;
in vec2 vUv;
in vec3 vWorldPosition;

uniform sampler2D myTextureSampler;
uniform vec3 viewPosition;

const vec3 lightDirection = vec3(0.0,-1.0,1.0);
const vec3 ambient = vec3(0.3,0.3,0.3);
const vec3 lightColor = vec3(0.0,0.8,0.0);

vec3 packNormalToRGB( const in vec3 normal ) {
    return normalize( normal ) * 0.5 + 0.5;
}

void main()
{
    vec3 viewDir = normalize(viewPosition - vWorldPosition);
    float diffuse = max(0.0,dot(vNormal,lightDirection));
    float specular = max(0.0,dot(vNormal,viewDir));
    vec3 reflectDir = normalize(reflect(-lightDirection,vNormal));

    if(diffuse ==0.0)
    {
        specular = 0.0;
    }
    else
    {
        specular = pow(specular , 100);
    }

    // 法线贴图
    color = vec4(packNormalToRGB(vNormal),1.0);
    //color = vec4(ambient + specular + lightColor * diffuse,1.0);
})EOF";

template <class T> void printMatrix(T mat)
{
    for (size_t r = 0; r < mat.numOfRows(); ++r)
    {
        cout << '|';
        for (size_t c = 0; c < mat.numOfCols(); ++c)
        {
            cout << mat[r * mat.numOfCols() + c];
            if (c != mat.numOfCols() - 1)
            {
                cout << ",\t";
            }
        }
        cout << '|' << endl;
    }
    cout << endl;
}
void printMatrix4(const Matrix4 &mat)
{
    printMatrix(mat);
}

class Application : public ApplicationCoreProfile
{
  private:
    OpenGLShaderProgram *program = nullptr;
    Texture texture;
    PerspectiveCamera *camera = nullptr;
    Scene *scene = nullptr;
    OpenGLRenderer *renderer = nullptr;
    Uniform<Vector3> *uniformViewPosition = nullptr;
    Uniform<Matrix4> *uniformModelViewMatrix = nullptr;
    Uniform<Matrix4> *uniformProjectionMatrix = nullptr;
    Uniform<Matrix3> *uniformNormalMatrix = nullptr;
    OrbitControl *control;

  public:
    Application(int argc, char **argv) : ApplicationCoreProfile(argc, argv)
    {
    }
    ~Application()
    {
        camera->release();
        if (program != nullptr)
        {
            program->release();
        }
        if (scene != nullptr)
        {
            scene->release();
        }
        if (renderer != nullptr)
        {
            renderer->release();
        }
    }

    void init() override
    {
        AutoReleasePool();

        ApplicationCoreProfile::init();

        camera = CreateRef<PerspectiveCamera>(25, mWindowSize.x / mWindowSize.y);

        camera->setPosition(Vector3(0, 0, 10));

        camera->lookAt(Vector3(0, 0, 0));

        scene = CreateRef<Scene>();

        //        auto geo = CreateRef<PlaneGeometry>();
        auto geo = CreateRef<BoxGeometry>();

        auto material = new RawShaderMaterial();

        auto mesh = CreateRef<Mesh>(geo, material);

        auto dirLight = CreateRef<DirectionalLight>();

        dirLight->setPosition(Vector3(-1, 0, 1).normalize());

        scene->add(dirLight);

        control = new OrbitControl(mInput, camera);

        scene->add(mesh);

        scene->add(control);

        renderer = new OpenGLRenderer(mWindow, mInput);

        material->shaderSource(EShaderType::E_VERTEX, shader_vert);

        material->shaderSource(EShaderType::E_FRAGMENT, shader_frag);

        material->setDepthTest(true);

        material->setDepthWrite(true);

        uniformModelViewMatrix = new Uniform<Matrix4>();
        uniformProjectionMatrix = new Uniform<Matrix4>();
        uniformNormalMatrix = new Uniform<Matrix3>();
        uniformViewPosition = new Uniform<Vector3>();

        material->setUniformByName("normalMatrix", uniformNormalMatrix);
        material->setUniformByName("modelViewMatrix", uniformModelViewMatrix);
        material->setUniformByName("projectionMatrix", uniformProjectionMatrix);
        material->setUniformByName("viewPosition", uniformViewPosition);
    }
    void render(double elapse) override
    {

        control->update(elapse);

        if (mInput->getKey(EKeyCode::Tab))
        {
            auto currState = mInput->getCursorState();
            mInput->setCursorState(currState == IInput::ECursorState::Normal ? IInput::ECursorState::Disable
                                                                             : IInput::ECursorState::Normal);
        }

        Matrix4 modelWorldMatrix;
        Matrix4 projectionMatrix = camera->getProjectionMatrix();
        Matrix4 viewMatrix = camera->getMatrixWorldInverse();

        Matrix4 modelViewMatrix = viewMatrix * modelWorldMatrix;
        Matrix3 normalMatrix;
        normalMatrix.getNormalMatrix(modelViewMatrix);

        uniformModelViewMatrix->setValue(modelViewMatrix);
        uniformProjectionMatrix->setValue(projectionMatrix);
        uniformNormalMatrix->setValue(normalMatrix);
        uniformViewPosition->setValue(camera->getPosition());

        renderer->render(scene, camera);
    }
};

int main(int argc, char **argv)
{
    Application app(argc, argv);
    return app.run();
}
